Second Life of a Hungarian SharePoint Geek

October 21, 2011

How to deploy a custom field with custom properties from a feature – the missing second part

Filed under: Custom fields, Deployment, Features, PowerShell, SP 2010 — Tags: , , , , — Peter Holpar @ 14:49

Only readers following my blog from the beginning may remember my post I wrote about the deployment of custom fields having custom properties more than three years ago. Of course that post is about WSS 3.0.

At that time I described how to include the custom properties into the field definition even if these properties are not declared by the XSD validation schema. However, injecting the custom properties the way I described there did not have effect on the values of the custom properties the custom field actually deployed with. I promised then that in the next part I would show you how to resolve this issue. It’s better late than never, the second part comes now, updated for SharePoint 2010.

The trick to achieve our goal seemed originally to be really simple. The base idea behind  it was that an instance of the field must be created sooner or later before usage, and the values we would like to push into the custom properties were available on object creation in the field schema deployed.

I’ve modified the constructors of the custom field, adding a call to my custom InitializeProps method.

  1. public SPFieldRegEx(SPFieldCollection fields, string fieldName)
  2.     : base(fields, fieldName)
  3. {
  4.     InitializeProps();
  5. }
  6.  
  7. public SPFieldRegEx(SPFieldCollection fields, string typeName, string displayName)
  8.     : base(fields, typeName, displayName)
  9. {
  10.     InitializeProps();
  11. }

The InitializeProps method is responsible for reading up the deployed custom property values from the field schema and setting them to the standard custom location in the schema.

  1. // in the custom field feature definition
  2. private readonly string customNamespaceUri = "http://schemas.grepton.com/sharepoint/";
  3.  
  4. private void InitializeProps()
  5. {
  6.     String regEx = (String)GetCustomProperty("RegEx");
  7.  
  8.     // value of custom property is null if not yet set
  9.     if (regEx == null)
  10.     {
  11.  
  12.         // load the field schema into an XML document
  13.         XmlDocument schemaXml = new XmlDocument();
  14.         schemaXml.LoadXml(SchemaXml);
  15.  
  16.         XmlNode fieldNode = schemaXml.SelectSingleNode("Field");
  17.  
  18.         if (fieldNode != null)
  19.         {
  20.             InitCustomProperty(fieldNode, "RegEx");
  21.             InitCustomProperty(fieldNode, "ErrMsg");
  22.             InitCustomProperty(fieldNode, "MaxLen");
  23.         }
  24.  
  25.     }
  26.  
  27. }
  28.  
  29. private void InitCustomProperty(XmlNode fieldNode, String custPropName)
  30. {
  31.     XmlAttribute custPropOrigAttr = fieldNode.Attributes[custPropName, customNamespaceUri];
  32.     // should not be null, but we check it
  33.     if (custPropOrigAttr != null)
  34.     {
  35.         SetCustomProperty(custPropName, custPropOrigAttr.Value);
  36.     }
  37. }

After deploying my custom field, I’ve checked my field at the Change Site Column page and was happy to see my custom property values there:

image

However, it turned out quickly that the schema of my field was not updated as expected:

  1. <Field
  2.   ID="{54634385-A8AC-4898-BF24-E533EB23444F}"
  3.   Name="RegExField"
  4.   DisplayName="RegExField"
  5.   StaticName="RegExField"
  6.   Group="Grepton Fields"
  7.   Type="SPFieldRegEx"
  8.   Sealed="FALSE"
  9.   AllowDeletion="TRUE"
  10.   SourceID="http://schemas.microsoft.com/sharepoint/v3/fields"
  11.   Description="This is the RegEx field"
  12.   grp:RegEx="[0-9]"
  13.   grp:MaxLen="20"
  14.   grp:ErrMsg="Error!"
  15.   xmlns:grp="http://schemas.grepton.com/sharepoint/" />

Of course, pressing OK on the Change Site Column page updated the field schema with the custom properties:

  1. <Field
  2.   ID="{54634385-A8AC-4898-BF24-E533EB23444F}"
  3.   Name="RegExField"
  4.   DisplayName="RegExField"
  5.   StaticName="RegExField"
  6.   Group="Grepton Fields"
  7.   Type="SPFieldRegEx"
  8.   Sealed="FALSE"
  9.   AllowDeletion="TRUE"
  10.   SourceID="http://schemas.microsoft.com/sharepoint/v3/fields"
  11.   Description="This is the RegEx field"
  12.   grp:RegEx="[0-9]"
  13.   grp:MaxLen="20"
  14.   grp:ErrMsg="Error!"
  15.   xmlns:grp="http://schemas.grepton.com/sharepoint/"
  16.   Required="FALSE"
  17.   EnforceUniqueValues="FALSE"
  18.   Version="1">
  19.   <Customization>
  20.     <ArrayOfProperty>
  21.       <Property>
  22.         <Name>RegEx</Name>
  23.         <Value
  24.           xmlns:q1="http://www.w3.org/2001/XMLSchema"
  25.           p4:type="q1:string"
  26.           xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">[0-9]</Value>
  27.       </Property>
  28.       <Property>
  29.         <Name>MaxLen</Name>
  30.         <Value
  31.           xmlns:q2="http://www.w3.org/2001/XMLSchema"
  32.           p4:type="q2:double"
  33.           xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">20</Value>
  34.       </Property>
  35.       <Property>
  36.         <Name>ErrMsg</Name>
  37.         <Value
  38.           xmlns:q3="http://www.w3.org/2001/XMLSchema"
  39.           p4:type="q3:string"
  40.           xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">Error!</Value>
  41.       </Property>
  42.     </ArrayOfProperty>
  43.   </Customization>
  44. </Field>

That gave me the idea that I should try to deploy my custom field using this schema. I removed my custom attributes from the Field element and used the following XML to deploy the field.

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  3.   <Field
  4.     ID="{54634385-A8AC-4898-BF24-E533EB23444F}"
  5.     Name="RegExField"
  6.     DisplayName="RegExField"
  7.     StaticName="RegExField"
  8.     Group="Grepton Fields"
  9.     Type="SPFieldRegEx"
  10.     Sealed="FALSE"
  11.     AllowDeletion="TRUE"
  12.     SourceID="http://schemas.microsoft.com/sharepoint/v3/fields"
  13.     Description="This is the RegEx field"
  14.     Version="1">
  15.     <Customization>
  16.       <ArrayOfProperty>
  17.         <Property>
  18.           <Name>RegEx</Name>
  19.           <Value
  20.             xmlns:q1="http://www.w3.org/2001/XMLSchema"
  21.             p4:type="q1:string"
  22.             xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">[0-9]</Value>
  23.         </Property>
  24.         <Property>
  25.           <Name>MaxLen</Name>
  26.           <Value
  27.             xmlns:q2="http://www.w3.org/2001/XMLSchema"
  28.             p4:type="q2:double"
  29.             xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">20</Value>
  30.         </Property>
  31.         <Property>
  32.           <Name>ErrMsg</Name>
  33.           <Value
  34.             xmlns:q3="http://www.w3.org/2001/XMLSchema"
  35.             p4:type="q3:string"
  36.             xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">Error!</Value>
  37.         </Property>
  38.       </ArrayOfProperty>
  39.     </Customization>
  40.   </Field>
  41. </Elements>

I’ve removed my custom codes described above either, and tried to deploy the field.

Voilà! The field is deployed successfully, including the custom properties. At that point I really don’t understand if it would work for WSS 3.0 and if so, how I missed to find that solution three years ago. The main point it is working now and requires no hacking at all.

A mystical observation is that after deploying the field I was not able to get a reference for the new field using PowerShell:

$avFields = $web.AvailableFields
$avFields["RegExField"]

At the same time, the field is visible on the web UI, and the following C# code finds it:

SPFieldCollection avFields = web.AvailableFields;
SPField field = avFields["RegExField"];

If I create the field from code, PowerShell finds that either:

SPFieldCollection fields = web.Fields;
SPField fieldCode = new SPField(fields, "SPFieldRegEx", "RegExCode");
fields.Add(fieldCode);

If you know the reason for that, let me know, please!

Advertisements

February 15, 2010

Multiple column lookup field for SharePoint

Filed under: Custom fields, SharePoint — Tags: , — Peter Holpar @ 03:23

I think I’m not alone missing the possibility in WSS 3.0 to include multiple columns from the target list when creating a lookup or user field in a list.

If you have read my former posts about custom fields and reordering of list fields you already know what you need to create such field types yourself.

In this post I won’t include lengthy code snippets, instead I’ve created a CodePlex project for the source code.

To illustrate the working of the fields I’ve created a custom list called Companies in SharePoint using the following fields:

image

I’ve added the following items to the list:

image

Next I’ve created a list called Contracts, and added my custom multi column lookup field (let’s call it master field) as shown on the following figure:

image

You might notice that the user interface of the field settings is not very sophisticated. You are absolutely right, it is not very error prone to specify the internal names of the related fields (the ones you would like to include into the list additionally to the original lookup field) using a text field separated by a pipe character (‘|’). In this implementation I simply inherited the original FieldEditorUserControl of the lookup field. In forthcoming releases I might have more time to create an advanced one, but in the current phase it was not the most important part of the development.

After adding your new field you can see that only the master field is visible on the Columns section.

image

But when you check the field order page, you can see that there are additional columns for your related fields as well. It is important that our master field is ordered automatically to the last position, as it must be able to interact with related fields on edit and new list forms.

image

Furthermore, if you add the master field to the default view on creation, all related fields are automatically added as well.

image

In the next step I create a new item. Notice, that only the master field is visible on the form.

image

After adding some additional items I had the following content in the Contracts list:

image

You can see that related fields are filled automatically.

If you go back to the field settings page of the master field, you can modify the value of the related fields (add or remove fields). Values in columns for added related fields are automatically populated as well, however I should note that this pattern might be not ideal if you have a lot of items in your list or there are multiple parallel editors and enabled check-outs, as it can cause performance issues or conflicts due to checked-out items.

There is an additional field that is very similar but it is for user field.

You can add the custom field to you list as shown here:

image

And here is a list item having the custom field:

 image

Here are the internal name – display name pairs of some common user fields you might be interested in:

Name – Account
EMail – Work e-mail
Department – Department
Title – Name
FirstName – First name
LastName – Last name
WorkPhone – Work phone
Office – Office
UserName – User name

If you would like to enable multiple values for the field, it is important to install the following update on the server:

Description of the Windows SharePoint Services 3.0 Cumulative Update Server hotfix package (Sts-x-none.msp): December 15, 2009

It includes the fix for this issue:

"You develop a custom field type that inherits from the SPFieldLookUp class. You want to store multiple values in a field of that custom field type. Therefore, you set the AllowMultipleValues property to True. However, after you set the AllowMultipleValues property to True, the field type is displayed as Lookup instead of the custom field type."

January 9, 2010

Creating real multi column fields for SharePoint

Filed under: Custom fields, SharePoint — Tags: , — Peter Holpar @ 01:35

SharePoint contains out of the box support for multi column fields. The base of these fields is the SPFieldMultiColumn class. The ParentType of the field definition should be MultiColumn  for these columns.

A typical sample for multi column field type is the US Address Field with MapPoint Picker Dialog. If you have ever created or worked with such field types you know it has several drawbacks that derive mainly from the simple fact, that despite of the field type name, values of the “multi” columns are stored in a single column, separated by “;#” characters.

Let’s see some of these drawbacks:

  1. You cannot handle the individual columns of the field separately on the list views, for example, include only the US address state column in the view, but no street address.
  2. The same is true for ordering or filtering the items on the list views. You cannot create a filter on US address state column or order by the street address.
  3. It’s not possible to create CAML query filters for the individual columns of the field.
  4. You cannot create managed property for search on the individual columns of the field.

What are the alternatives for this situation? Why don’t we create a real multi column field? What are the requirements for this kind of field?

  1. That kind of custom field should have a master field, and slave fields.
  2. Only the master field should be available when you add the field to the list or content type.
  3. When you add the master field, it should automatically create the slave fields.
  4. When you remove the master field, it should automatically remove slave fields.
  5. Only the master field should have its field control, that addresses (read and write) slave field values, display them in edit or display mode and does all the validation for the fields.
  6. The consequence of the previous statement is that only the master field should be visible on the new / edit / display forms, the slave fields must be invisible.
  7. If you don’t want the master field to provide the user interface for the slave fields, then you should not hide the slave fields as described above. This situation can be useful sometimes and requires a little different handling of fields as described here. We don’t discuss this situation in the current post.

In this post I will show you code for real multi column fields only for lists, but you can apply the same technique for content types either.

The following codes should be included in the code of the master field class. You can use built-in field types as slave fields. In the current example I will use only a single text slave field. You can extend that based on the sample.

When the master field is added, we add all of our slave fields either.

  1. public override void OnAdded(SPAddFieldOptions op)
  2. {
  3.     AddField("yourSlaveField");
  4.     base.OnAdded(op);
  5. }

We get the fields of the list, set some important properties and add the slave field as a new text field to the field collection. Of course, you are free to create other field types, like dates, numbers or yes/no values if you wish.

We should set the value of the ReadOnlyField property to true. Using this setting the slave field will be hidden on the edit and new form. You can achieve the same by setting the ShowInEditForm and ShowInNewForm properties of the slave fields to false.

To hide the slave field on the display form we set the value of the ShowInDisplayForm property of the field to false.

We set the value of the ShowInListSettings property to false. This setting hides the slave field on the List Settings page, so users can not manipulate that directly from the user interface.

Finally, we set the value of the RelatedField property of the slave field to the value of the Title property of the master field. We will use this later to get the slave fields related to the master field when removing the master field from the list.

  1. private SPField AddField(string fieldName)
  2. {
  3.     SPList list = SPContext.Current.List;
  4.     SPFieldCollection fields = list.Fields;
  5.     SPField field = null;
  6.  
  7.     if (!String.IsNullOrEmpty(fieldName))
  8.     {
  9.         field = new SPFieldText(fields, SPFieldType.Text.ToString(), fieldName);
  10.         field.ReadOnlyField = true;
  11.         field.ShowInDisplayForm = false;
  12.         field.ShowInListSettings = false;
  13.         field.RelatedField = Title;
  14.         fields.Add(field);
  15.         list.Update();
  16.     }
  17.     return field;
  18. }

On the master field removal we also remove all of the slave fields.

  1. public override void OnDeleting()
  2. {
  3.     RemoveRelatedFields();
  4.     base.OnDeleting();
  5. }

We identify these fields using the RelatedField property. If the value of this property equals to the Title property of the master field, the field is considered a slave field of the current field.

There are two tricks in this method. First, we should iterate the list fields using a for loop, iterating from the last item to the first one to avoid the catches of modifying the collection being iterated on. Second, we have to set the ReadOnlyField property of the slave field to false and call the Update method to propagate changes. Without that we would get an exception when trying the field deletion.

  1. private void RemoveRelatedFields()
  2. {
  3.     SPFieldCollection fields = SPContext.Current.List.Fields;
  4.     for (int i = fields.Count – 1; i > -1; i–)
  5.     {
  6.         SPField field = fields[i];
  7.         if (field.RelatedField == Title)
  8.         {
  9.             field.ReadOnlyField = false;
  10.             field.Update();
  11.             fields.Delete(field.InternalName);
  12.         }
  13.     }
  14. }

When rendering the master field control, you should read the values of the slave fields, and when the field is in edit or new mode and being saved, you should catch the save event and store the values from the field control to the slave fields.

In this post I won’t detail how you can access other field values from your field control. You can read about that in my former posts:

  • Interacting with other fields from a custom field
  • Interacting with other fields from a custom field, 2nd part
  • Cross field, cross item, cross list or even more complicated validations on SharePoint forms
  • Here you find the info required to create your own save method in the master field:

    After adding the master field to the list, the slave field won’t be visible on the List Settings page, the display, new and edit forms, but it will be visible on the Edit View page, so you can add master and slave fields independently, and can use them to sort and filter the items in the views.

    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.

    December 30, 2009

    Interacting with other fields from a custom field, 2nd part

    Filed under: Custom fields, SharePoint — Tags: , — Peter Holpar @ 22:21

    In the first part of this post I left an open issue. There was a problem with setting the fields read only.

    Since then I’ve found the source of the incorrect value in the display (read only) mode of the field on the new form and provide a workaround for that issue here, that is unfortunately a bit of hacking.

    First, I should note, that as I observed, the value displayed in this case has no effect on the value saved into the item, but default values have also no effect. Furthermore, when the item is opened and saved in the edit mode, the former field values might be also lost.

    I found that when the field control mode is set to display on the new form in our case, the value of the PreviewValueTyped property of the related field is displayed. That is FieldName field value. for the text fields (replace FieldName with the actual field name), 1,234.56 for numeric fields, the actual date and time for date type fields, default value or if there is no default value, the first option for choice fields.

    The source of the issue seems to be is that the ItemFieldValue property and the ListItemFieldValue property of the BaseFieldControl return the value of the PreviewValueTyped if the ItemId property is zero, that is the case for a new item. I should note that the obfuscated RenderFieldForDisplay method also references the PreviewValueTyped property. I’ve tried a hack and set the ItemId to –1, and it seems to solve part of the issue, the field value is displayed correctly.

    But the values of the affected fields in the item are still not saved correctly, since setting the ItemId to –1 has a side effect that it resets the ItemFieldValue and the ListItemFieldValue properties to null. To correct this behavior I backed up the value of the ListItemFieldValue property before setting the ItemId to –1, and restore it at the end. I cannot omit setting the ItemId to –1, since while the ItemId is zero, setting the value of the ListItemFieldValue property has no effect.

    1. protected void SetFieldReadOnly(String fieldName)
    2. {
    3.     BaseFieldControl fieldControl = GetFieldControlByName(fieldName);
    4.     Object value = fieldControl.ListItemFieldValue;
    5.     fieldControl.ControlMode = SPControlMode.Display;
    6.     if (ControlMode == SPControlMode.New)
    7.     {
    8.         fieldControl.ItemId = -1;
    9.         fieldControl.ListItemFieldValue = value;
    10.     }
    11. }

    After these modifications the new form works as expected, but there is an issue with the edit form. Even the correct values are displayed, empty text is saved for text field, and the first item is set for the choice field when saving the item.

    This issue was solved by placing the code from the OnLoad method to the OnInit method of the field control.

    An important note finally. It is mandatory to place the custom field that tries to access other fields using the GetFieldControlByName method introduced in the former post after the last field it references. You can change the field order using the Change Field Order page (List Settings / Column ordering), or you can do it using code. If you don’t do that this way, the method will not find the field on the page, as it is added only after the current field is loaded.

    December 25, 2009

    Interacting with other fields from a custom field

    Filed under: Custom fields, SharePoint — Tags: , — Peter Holpar @ 19:28

    In my recent post I wrote about hiding custom fields just to interact with other fields on the form. Today I will show you how you can create the interaction itself between the fields.

    It requires some little tricks, but after we solve the exercise you will see that it is not so complicate and in the forthcoming post I will show you some very powerful example to demonstrate you the usability of this technique.

    The main point of the interaction is to get the reference to the other field controls. As I did last time, I will first introduce an interesting approach that – sad but true – does not work.

    On the forms the fields are rendered within a ListFieldIterator control. Individual fields of type SPField can be addressed through the Fields collection (inherited from FormComponent) of this class that returns an SPFieldCollection. SPField class has a property called FieldRenderingControl that returns BaseFieldControl. Since the actual ListFieldIterator can be accessed through the parent chain of the actual field control, at the first sight it seems to be a good idea to get the reference for another field using the following really long line of code:

    BaseFieldControl fieldControl = ((Microsoft.SharePoint.WebControls.ListFieldIterator)this.Parent.Parent.Parent.Parent.Parent).Fields[fieldName].FieldRenderingControl;

    Unfortunately that is not so simple. Although we got a valid reference to a field control, if you try to manipulate its properties, or just to read them, it turns out to be an other instance, not the one displayed on the forms. That is because the FieldRenderingControl returns a new instance of a subclass of the BaseFieldControl, that has really no relation to the actual form.

    What other ways there are to get the reference field control? We know that it is a control on the page, but we don’t want to iterate through all of the controls just to find the field. Fortunately the BaseFieldControl class implements a specific interface IValidator and thus included in the Validators collection of the Page class. That is really nice, and we will depend on this behavior in a later post when implementing custom runtime validation of fields.

    But for now it is enough to get the reference to the field control using a custom method like this:

    1. protected BaseFieldControl GetFieldControlByName(String fieldNameToSearch)
    2. {
    3.     String iteratorId = GetIteratorByFieldControl(this).ClientID;
    4.     foreach (IValidator validator in Page.Validators)
    5.     {
    6.         if (validator is BaseFieldControl)
    7.         {
    8.             BaseFieldControl baseField = (BaseFieldControl)validator;
    9.             String fieldName = baseField.FieldName;
    10.             if ((fieldName == fieldNameToSearch) &&
    11.                 (GetIteratorByFieldControl(baseField).ClientID == iteratorId))
    12.             {
    13.                 return baseField;
    14.             }
    15.         }
    16.     }
    17.     return null;
    18. }
    19.  
    20. private ListFieldIterator GetIteratorByFieldControl(BaseFieldControl fieldControl)
    21. {
    22.     return (Microsoft.SharePoint.WebControls.ListFieldIterator)this.Parent.Parent.Parent.Parent.Parent;
    23. }

    In this method we iterate through the validators on the page, and do some checks in the following order:

    1. Is the field derived from the BaseFieldControl class?
    2. Is the field name the same we are looking for?
    3. An extra check just to be sure: is the field rendered in the same ListFieldIterator as the current field control. Without this check it is possible to have multiple forms on the same page having fields with the same name. We really don’t want to interact with fields in other forms. At least, not now…

    Let’s see some simple practical example for usage of this method. Of course, in a production environment you should add some extra logic for null values, exception handling and tracing/logging, but I try to keep it simple now. All of the code snippets are for the field control class that is derived from the BaseFieldControl class.

    In the first example we set the field control mode to display. That can you use to prevent users to modify the field value, for example based on the user’s permissions or the value of other field.

    1. protected void SetFieldReadOnly(String fieldName)
    2. {
    3.     BaseFieldControl fieldControl = GetFieldControlByName(fieldName);
    4.     fieldControl.ControlMode = SPControlMode.Display;           
    5. }

    Sometimes it is required to hide the field value too. The next example hides the field having the specified name using the same technique we used to inject invisible field to the form.

    1. protected void HideField(String fieldName)
    2. {
    3.     BaseFieldControl fieldControl = GetFieldControlByName(fieldName);
    4.     fieldControl.Visible = false;
    5.     fieldControl.Parent.Parent.Visible = false;
    6. }

    You can also set values of other fields using the same technique as illustrated in the code snippet below for a text field.

    1. protected void SetFieldValue(String fieldName, String fieldValue)
    2. {
    3.     TextField fieldText = (TextField)GetFieldControlByName(fieldName);
    4.     fieldText.Value = fieldValue;
    5.     fieldText.Text = fieldValue;
    6. }

    You might ask why I don’t show you the simplest case: reading field value. That is because it is really not so simple, and I will describe it in details in a forthcoming post.

    It might be useful to use these and similar methods in your custom field control and use that as the boilerplate class for more advanced fields.

    The next code snippet assumes that you have a custom list with fields Field1, Field2, Field3, Field4 (all of these of type Single line of text), the default Title field and Field5 of type Choice with values Value1, Value2 and Value3. Furthermore, I suggest you to create your custom field as a “hidden” field as described here.

    I’ve created an item in the list before adding our new field to the list columns. The following figure shows the values of the fields on the edit form. You can see there is nothing special on that:

    image

    We call the methods in the edit and new control modes of the field from the OnLoad method of the field control:

    1. if ((ControlMode == SPControlMode.Edit) || (ControlMode == SPControlMode.New))
    2. {
    3.     HideField("Field1");
    4.     SetFieldReadOnly("Field3");
    5.     SetFieldReadOnly("Field5");
    6.     SetFieldValue("Field4", "text");
    7. }

    After building, deploying, and adding the new field to the list, you should see the results immediately on the edit form:

    image

    It seems to be working perfectly, but if you would like to create you will notice that there is a minor issue with the code. Field3 should be empty, but it contains the text Field3 field value. The other read only field, Field5 of type Choice is displayed correctly.

    image

    See the second part of this post for the solution of the issue.

    Next time I try to plan to show you something more exciting using the same technique.

    December 20, 2009

    Injecting an invisible custom field to a form

    Filed under: Custom fields, SharePoint — Tags: , — Peter Holpar @ 05:44

    If you want to inject a custom field to a form just to interact with other fields on the form or do something that is not typical to do with custom columns (see my earlier post in this topic) one of the first problems you will face how to hide your field so that it will be included in the form but only in an invisible way.

    Let’s see some alternatives that do not work, just to learn, what the problem with them is.

    There are the ShowInDisplayForm, ShowInNewForm, ShowInEditForm properties of the SPField class. These are great ways to hide your field, but if you set these properties to false, the field will be not included to the form at all. That is not very useful for us if we need the code of the field to run in the context of the form.

    Our next try is to override the Visible property of the field control to return always false.

    1. public override bool Visible
    2. {
    3.     get
    4.     {
    5.         return false;
    6.     }
    7.     set
    8.     {
    9.         base.Visible = false;
    10.     }
    11. }

    But it does not help, our field is still visible on the form.

    It has the same effect (= nothing) if you set the Visible property of the field control in the OnLoad method to false:

    this.Visible = false;

    But we never give up!

    The solution to the issue is to set the Visible property of the TemplateContainer that contains the field control. The Parent of the field control is a FormField, Parent of the Parent is the TemplateContainer we need. So our code that works in the OnLoad method seems like this:

    this.Parent.Parent.Visible = false;

    Setting the Visible property of the Parent’s Parent will set the Visible property of the control itself too.

    Important note, that if you need to override the default save event by overriding the UpdateFieldValueInItem method of the field control as described in my former post, then you should set the Visible property first to true, then set back to false, otherwise the UpdateFieldValueInItem method will not be called. That is because the OnLoad method of the BaseFieldControl checks for the visibility of the control, and call the UpdateFieldValueInItem only if the control is visible.

    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. }

    We typically do not want this kind of field to be included in any view of the list. When the field is added to the list, you should clear the Add to default view check box.

    image

    Unfortunately, I have not figured that out how to make this setting to the default one that user cannot change on the user interface.

    To disable users to add the field to views we can set the ShowInViewForms property to false when the field is added to the list, that is in the OnAdded method, as shown below:

    1. public override void OnAdded(SPAddFieldOptions op)
    2. {            
    3.     base.OnAdded(op);
    4.     if ((!this.ShowInViewForms.HasValue) || ((this.ShowInViewForms.HasValue) && (!this.ShowInViewForms.Value)))
    5.     {
    6.         this.ShowInViewForms = false;
    7.         this.Update();
    8.     }
    9. }

    If this property is set to false, the field will be not included in the Edit View page in the list of available fields.

    December 9, 2009

    How to override the default item save event using custom fields

    Filed under: Custom fields, SharePoint — Tags: , — Peter Holpar @ 01:26

    In my former post I wrote about the approach to use custom field types as an alternative way for list item event handling in SharePoint. In the current post I would like to show you how to inject code into the edit item or new item forms using a custom field that intercepts the default item save event.

    You can put the event handling in a custom field that has some other role on the user interface, or it can be a custom field without its own UI that serves only the special role of event handling. I will show you an example for the latter one in a later post.

    You might ask why is it useful to be able to do that. Let’s see some examples for the usage.

    Do actions before saving the item, like:

    · Cross-field or even cross-list validation of the item, for example, the start date of the event must be less than the end date, or the value of the Affected tasks field for an item must be less than the count of the task items in another list.

    · Extra permission checks, for example, based on the item fields, like only the creator of the item can modify the status.

    · Add validation errors to the affected fields and cancel the save, if the above validations fail.

    · Update field values on the fly for the same item or even an item in another list, like call a web service using the login name of the modifier and store the results in one of the fields.

    Do actions after saving the item, like:

    · Notify back-end systems about the change, for example, call a web service, update / insert a record in an SQL server table or create a new SharePoint list / site based on the field values.

    · Redirect the user to another page.

    · Set permissions on the item, for example, set write permissions using elevated privileges for the users or groups that were specified in a Person or Group field. This may be useful since it enables users with edit permissions to set item security.

    There are two similar ways to intercept the base item save process, both of them is provided by the BaseFieldControl class, so you should put the code in the field control class of the custom field.

    First approach is to override the UpdateFieldValueInItem method:

    1. public override void UpdateFieldValueInItem()
    2. {
    3.     Page.Validate();
    4.     if (Page.IsValid)
    5.     {
    6.         // do actions before save
    7.         // do the save if required
    8.         base.UpdateFieldValueInItem();
    9.         // do actions after save
    10.     }
    11.     else
    12.     {
    13.         // do actions instead of save
    14.     }
    15. }

     
    The another alternative is to attach your own event handler to the OnSaveHandler property of the SPFormContext class:
     
    1. protected override void OnInit(EventArgs e)
    2. {
    3.     base.OnInit(e);
    4.     // add save handler only in New and Edit modes
    5.     if ((SPContext.Current.FormContext.FormMode == SPControlMode.New) || (SPContext.Current.FormContext.FormMode == SPControlMode.Edit))
    6.         SPContext.Current.FormContext.OnSaveHandler += new EventHandler(MyHandler);
    7. }
    8. protected void MyHandler(object sender, EventArgs e)
    9. {
    10.     Page.Validate();
    11.     if (Page.IsValid)
    12.     {
    13.         // do actions before save
    14.         // do the save if required
    15.         SPContext.Current.ListItem.Update();
    16.         // or you can save the item using this line of code either
    17.         //SaveButton.SaveItem();
    18.         // do actions after save
    19.     }
    20.     else
    21.     {
    22.         // do actions instead of save
    23.     }
    24. }

    If you don’t call the methods that save the item to the SharePoint list your form can be used as a front-end for a back-end system as well, for example, you can call a web service or insert SQL records without affecting the content of the SharePoint list. But that is probably not the most ergonomic usage of SharePoint and the easiest way to create web based user interface for back-end systems.

    December 3, 2009

    Custom field types as an alternative way for list item event handling in SharePoint

    Filed under: Custom fields, SharePoint — Tags: , — Peter Holpar @ 01:33

    The availability of custom columns is one of the coolest features in SharePoint. And I think it is the most underutilized one either. The samples found on the web are mostly limited to creating editors for simple data types with custom validation logic like phone numbers or general regular expression validation, or composite (multicolmn) fields, like address field type, having field columns for state, city, street, street number.

    Not much words are said about that since custom field controls are part of the form rendering and list item saving process they provide an alternative for handling events in SharePoint.

    Let’s see the advantages and drawbacks of custom field types compared to the traditional event receiver based approach.

    Pros:

    · Custom fields are easy to parameterize per list on the admin user interface through the custom field properties (despite the issues with these properties) when creating the field for the list or for the web, or from code using the same custom properties. Event receivers can be parameterized only through laborious config files or config database tables, or the SPEventReceiverDefinition.Data property that has its limitation of length as 255 characters.

    · You can handle events that are not possible to handle with event receivers, like form initialization where you can set defaults for other fields based on your custom logic, or form post back caused by other field, where you can modify the content of other field control (for example, cascading lookups) or even hide / show other fields based on actual field control content.

    · You can also handle „item displayed” type events, also not possible using event receivers.

    · You can handle field addition / removal events where you can manipulate other fields, like setting defaults, adding extra fields, or you can made other customization as well, like register an event receiver to the list.

    · You can interact with the user interface, not trivial in case of an event receiver, so it is easier to create more user friendly validation.

    · You can attach multiple instances of the same custom field type for the same list, and you can set individual parameter values for each. That is a great way for interoperation between the instances.

    Cons:

    · You cannot handle after (-ed) events with custom fields. That kind of events happen after the forms are posted back, so the field controls are out of scope.

    · It requires some tricks to access values of other fields, but after you learn the basics it is not much worse than BeforeProperties and AfterProperties.

    · By default custom fields has a user interface that may be a drawback when you don’t need that. Of course, you can handle this issue as well, but it requires a few extra lines of code.

    · The most important drawback is that custom field controls can help only when the item is displayed / edited on the standard user interface. If the item is edited using web service, custom ASP.NET controls or in the datasheet view they are rather useless.

    In the forthcoming posts I’ll try to provide to share my experience with custom fields to illustrate some exciting usage examples of custom fields.

    October 10, 2009

    Changing system properties for an item from the SharePoint user interface – part 1

    Filed under: Custom fields, SharePoint — Tags: , — Peter Holpar @ 03:43

    UPDATE: See the source code of this post on CodePlex.

    A few month ago I had a thread at MSDN SharePoint – Development and Programming forum about changing file creation dates in SharePoint.

    You might know that there are tools that make it possible to change system properties of files in file system. You can read more about it here:
    How to change the last modified date, creation date, and last accessed date for files and folders

    There is a nice tip in Steven Van de Craen’s Blog about how one can change these properties from code in SharePoint:
    SharePoint 2007: Update system properties (Created, Created By, Modified, Modified By)

    I’ve decided to implement a solution that has some kind of user interface and makes it user friendly to manipulate this properties. It would have several advantage to be able to set these values (just joking, don’t take me too serious):
    – No boss, it was not me, who uploaded the report that contains invalid numbers to the site.
    – Yes boss, the document was uploaded by the deadline, probably you checked that wrong yesterday.

    My first idea was a WinForms application but it would have a few drawbacks:
    – Require me to create the WinForms application, design user interface.
    – I have to implement the listing and selecting the SharePoint items for which I want to set the properties.
    – As I do not know how to set these properties remotely, it will be limited to be run only on the server, or I would have to wrap the functionality in a custom web service.

    So I’ve changed my mind, and decided to implement the tool as a server side solution. As the lists and items are displayed out of the box on SharePoint web UI, it was the quickest to choose a way that extends these forms and that was developing a custom field.

    After the solution is deployed to the server, a new field type can be selected when configuring your list settings and choosing Create Column:

    image_thumb4

    As we will see it soon, this field should not be added to the list views:

    image_thumb5

    A typical screenshot of the field when an admin user is logged in:

    image_thumb1

    The same field for a non-admin user is rendered like that:

    image_thumb3 

    Currently there is no download provided for this post, but I plan to publish this solution on CodePlex later either individually or as part of an existing package. Until that if you would like to use the code, feel free to copy that from this page, but don’t forget to change the public key token to match the one in your project, deploy the assembly into GAC, and add the control as SafeControl in the web.config of your site.

    It is important to note, that the solution is provided “AS IS”, meaning not fully tested, data loss or side effects might be possible, and no support is guaranteed, etc. By using the code above you explicitly agree with this conditions. It might be also dangerous for your business to include this control on your site and allow users to change system properties. Just mention that for you to not say later I have not forewarned you about that.

    My solution derives from a simple Text field, but it really has not much importance, as this field does not store any values, it is just a dummy field to inject user interface element for list items.

    The XML schema for the field (fldtypesCorrector.xml, located by default at C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\XML) contains nothing special. The field is not intended to be included in a view, but if it is added to one, the we display simply “Use the edit form” caption for each list items.

       1: <?xml version="1.0" encoding="utf-8" ?>

       2: <FieldTypes>

       3:   <FieldType>

       4:     <Field Name="TypeName">CorrectorField</Field>

       5:     <Field Name="ParentType">Text</Field>

       6:     <Field Name="TypeDisplayName">Corrector field</Field>

       7:     <Field Name="TypeShortDescription">System properties editor</Field>

       8:     <Field Name="UserCreatable">TRUE</Field>

       9:     <Field Name="ShowOnListAuthoringPages">TRUE</Field>

      10:     <Field Name="ShowOnSurveyAuthoringPages">TRUE</Field>

      11:     <Field Name="ShowOnDocumentLibraryAuthoringPages">TRUE</Field>

      12:     <Field Name="ShowOnColumnTemplateAuthoringPages">TRUE</Field>

      13:     <Field Name="FieldTypeClass">Grepton.SharePoint.FieldTypes.SPFieldCorrector,CorrectorField,Version=1.0.0.0,Culture=neutral,PublicKeyToken=062a83cb41c3b1cb</Field>

      14:     

      15:     <PropertySchema>

      16:         <Fields></Fields>

      17:     </PropertySchema>

      18:       

      19:     <RenderPattern Name="DisplayPattern">

      20:         <HTML>Use the edit form</HTML>

      21:     </RenderPattern>

      22:       

      23:   </FieldType>

      24: </FieldTypes>

    It is displayed on the figure below:
     
    image6_thumb
     
    The rendering template for the field (CorrectorField.ascx) is also not too exciting. It contains only a DateTimeControl for both created and modified dates, and a PeopleEditor control for both created by and modified by. This file is located by default in C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\CONTROLTEMPLATES folder.
     
       1: <%@ Control Language="C#" Debug="true"  %>

       2: <%@Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

       3: <%@Register TagPrefix="SharePoint" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.WebControls"%>

       4:  

       5: <SharePoint:RenderingTemplate ID="CorrectorField" runat="server">

       6:     <Template>

       7:         Created:<br>

       8:         <SharePoint:DateTimeControl ID="CreatedDate" runat="server"/><br>    

       9:         Modified:<br>

      10:         <SharePoint:DateTimeControl ID="ModifiedDate" runat="server"/><br>    

      11:         Created by:<br>

      12:         <SharePoint:PeopleEditor ID="CreatedBy" MaximumEntities="1" MaximumHeight="1" MultiSelect="false" SelectionSet="User" runat="server" ValidatorEnabled="true" />

      13:         Modified by:<br>

      14:         <SharePoint:PeopleEditor ID="ModifiedBy" MaximumEntities="1" MaximumHeight="1" MultiSelect="false" SelectionSet="User" runat="server" ValidatorEnabled="true"    />

      15:     </Template>

      16: </SharePoint:RenderingTemplate>

    The information I’ve found on Karine Bosch’s blog about the controls mentioned above was a great help.
     
    Since we do nothing special (do not store or validate values) in our field, its class is rather simple too.
     
       1: using System;

       2: using System.IO;

       3: using System.Security.Permissions;

       4: using System.Text;

       5: using System.Text.RegularExpressions;

       6: using System.Xml;

       7: using System.Web;

       8: using System.Web.UI;

       9: using System.Web.UI.WebControls;

      10: using System.Web.UI.HtmlControls;

      11: using Microsoft.SharePoint;

      12: using Microsoft.SharePoint.WebControls;

      13: using System.ComponentModel;

      14: using System.Diagnostics;

      15:  

      16: namespace Grepton.SharePoint.FieldTypes

      17: {

      18:  

      19:     //Field inherits from the WSS base type SPFieldText

      20:     public class SPFieldCorrector : SPFieldText

      21:     {

      22:         public SPFieldCorrector(SPFieldCollection fields, string fieldName)

      23:             : base(fields, fieldName)

      24:         {

      25:         }

      26:  

      27:         public SPFieldCorrector(SPFieldCollection fields, string typeName, string displayName)

      28:             : base(fields, typeName, displayName)

      29:         {

      30:         }

      31:  

      32:         //Returns a field control for the page

      33:         public override BaseFieldControl FieldRenderingControl

      34:         {

      35:             get

      36:             {

      37:                 BaseFieldControl fldControl = new CorrectorFieldControl();

      38:                 fldControl.FieldName = InternalName;

      39:                 return fldControl;

      40:             }

      41:         }

      42:  

      43:         //Returns the field value object

      44:         public override object GetFieldValue(string value)

      45:         {

      46:             return null;

      47:         }

      48:  

      49:         //Performs all validation before the field is stored

      50:         public override string GetValidatedString(object value)

      51:         {

      52:             return String.Empty;

      53:         }

      54:  

      55:     }

      56:  

      57: }

    In the next part of this blog entry you can read more about the implementation of the custom field.

    Older Posts »

    Blog at WordPress.com.