Interacting with other fields from a custom field

 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 Fieldscollection (inherited from FormComponent) of this class that returns an SPFieldCollection. SPField class has a property calledFieldRenderingControl that returns BaseFieldControl. Since the actualListFieldIterator 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 theFieldRenderingControl returns a new instance of a subclass of theBaseFieldControl, 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 theBaseFieldControl 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 sameListFieldIterator 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 valuesValue1, 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

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 ItemFieldValueproperty and the ListItemFieldValue property of the BaseFieldControlreturn 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 thePreviewValueTyped 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 theListItemFieldValue 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 ListItemFieldValueproperty 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 GetFieldControlByNamemethod 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.

Leave a Reply

Your email address will not be published. Required fields are marked *