In a recent post I showed you the basic of SharePoint list form customizations via custom list filed iterators.
In this post I provide a sample that can serve as a base for your form customization needs. From the sample you will learn how to alter the default display mode of the form fields, for example, hide fields on the edit or display form, or display fields as read-only on edit form. As a bonus, I will show you how to inject your code into the saving mechanism of list items and override the default behavior.
Before writing our own code, let’s see how the default field rendering is working. The following code snippet is the CreateChildControls method of the Microsoft.SharePoint.WebControls.ListFieldIterator class.
- protected override void CreateChildControls()
- {
- this.Controls.Clear();
- if (this.ControlTemplate == null)
- {
- throw new ArgumentException("Could not find ListFieldIterator control template.");
- }
- for (int i = 0; i < base.Fields.Count; i++)
- {
- SPField field = base.Fields[i];
- if (!this.IsFieldExcluded(field))
- {
- TemplateContainer child = new TemplateContainer();
- this.Controls.Add(child);
- child.ControlMode = base.ControlMode;
- child.FieldName = field.InternalName;
- this.ControlTemplate.InstantiateIn(child);
- }
- }
- }
As you can see, hiding a field is quite straightforward, you should simply skip the field similarly to the IsFieldExcluded condition. In fact, the IsFieldExcluded property is a useful way to hide fields, but the method shown in the post hopefully provides a more flexible solution.
Displaying the field as read-only on the edit form would be possible through setting the ControlMode property of the TemplateContainer instance in the overriden version of CreateChildControls method.
Unfortunately, both ControlMode and FieldName properties are internal members of the TemplateContainer class, so there is no simple way to work with them in our custom CreateChildControls method.
That is where Reflection or dynamic methods come into the picture. In this case I chose the second one, and created the ILUtils helper class:
- public delegate void GenericSetter(object target, object value);
- public delegate object GenericGetter(object target);
- class ILUtils
- {
- ///
- /// Creates a dynamic setter for the property
- ///
- public static GenericSetter CreateSetMethod(Type targetType, String propName)
- {
- GenericSetter result = null;
- PropertyInfo propertyInfo = targetType.GetProperty(propName,
- BindingFlags.NonPublic | BindingFlags.Instance);
- if (propertyInfo != null)
- {
- MethodInfo setMethod = propertyInfo.GetSetMethod(true);
- if (setMethod != null)
- {
- Type[] arguments = new Type[2];
- arguments[0] = arguments[1] = typeof(object);
- DynamicMethod setter = new DynamicMethod(
- String.Concat("_Set", propertyInfo.Name, "_"),
- typeof(void), arguments, propertyInfo.DeclaringType);
- ILGenerator generator = setter.GetILGenerator();
- generator.Emit(OpCodes.Ldarg_0);
- generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
- generator.Emit(OpCodes.Ldarg_1);
- if (propertyInfo.PropertyType.IsClass)
- generator.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
- else
- generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);
- generator.EmitCall(OpCodes.Callvirt, setMethod, null);
- generator.Emit(OpCodes.Ret);
- result = (GenericSetter)setter.CreateDelegate(typeof(GenericSetter));
- }
- }
- return result;
- }
- ///
- /// Creates a dynamic getter for the property
- ///
- public static GenericGetter CreateGetMethod(Type targetType, String propName)
- {
- GenericGetter result = null;
- PropertyInfo propertyInfo = targetType.GetProperty(propName,
- BindingFlags.NonPublic | BindingFlags.Instance);
- if (propertyInfo != null)
- {
- MethodInfo getMethod = propertyInfo.GetGetMethod(true);
- if (getMethod != null)
- {
- Type[] arguments = new Type[1];
- arguments[0] = typeof(object);
- DynamicMethod getter = new DynamicMethod(
- String.Concat("_Get", propertyInfo.Name, "_"),
- typeof(object), arguments, propertyInfo.DeclaringType);
- ILGenerator generator = getter.GetILGenerator();
- generator.DeclareLocal(typeof(object));
- generator.Emit(OpCodes.Ldarg_0);
- generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
- generator.EmitCall(OpCodes.Callvirt, getMethod, null);
- if (!propertyInfo.PropertyType.IsClass)
- generator.Emit(OpCodes.Box, propertyInfo.PropertyType);
- generator.Emit(OpCodes.Ret);
- result = (GenericGetter)getter.CreateDelegate(typeof(GenericGetter));
- }
- }
- return result;
- }
- }
Above idea and most of the code are borrowed from this great post.
We will store display rule exceptions using FieldDisplayRuleItem instances. FieldDisplayRule determines if we should hide the field or display it as read-only. FieldNames and ControlModes stores the field names and control modes the rule applies to. We will see this class in action soon.
- // helper class to store display exception rules
- // you can add your own properties to extend logic
- public class FieldDisplayRuleItem
- {
- // list of field names the rule applies to
- public List<String> FieldNames { get; set; }
- // list of control modes the rule applies to
- public List<SPControlMode> ControlModes { get; set; }
- // the resulting display exeption rule
- public FieldDisplayRule Rule { get; set; }
- }
- public enum FieldDisplayRule
- {
- Hidden,
- Display
- }
Note: you can extend the FieldDisplayRuleItem class as your requirements dictate. For example if the items you work with have a Status field it is common to include this field in the rules. Of course, in this case you should alter the logic that selects the items to get the hidden / read-only fields as illustrated later.
Using the dynamic methods helper class it is easy to create a setter for the ControlMode and FieldName properties of the TemplateContainer class, that we can use in the overriden CreateChildControls method of our base class.
- public class BaseListFieldIterator : Microsoft.SharePoint.WebControls.ListFieldIterator
- {
- // create dynamic setter methods that wrap the internal
- // ControlMode and FieldName properties of the TemplateContainer class
- protected static GenericSetter set_TemplateContainer_ControlMode =
- ILUtils.CreateSetMethod(typeof(TemplateContainer), "ControlMode");
- protected static GenericSetter set_TemplateContainer_FieldName =
- ILUtils.CreateSetMethod(typeof(TemplateContainer), "FieldName");
- protected List<FieldDisplayRuleItem> _dynamicRules = new List<FieldDisplayRuleItem>();
- // get references for the frequently used objects
- protected SPSite _site = SPContext.Current.Site;
- protected SPWeb _web = SPContext.Current.Web;
- protected SPContext _context = SPContext.Current;
- protected SPFormContext _formContext = SPContext.Current.FormContext;
- protected override void CreateChildControls()
- {
- this.Controls.Clear();
- if (this.ControlTemplate == null)
- {
- throw new ArgumentException("Could not find ListFieldIterator control template.");
- }
- for (int i = 0; i < base.Fields.Count; i++)
- {
- SPField field = base.Fields[i];
- String fieldName = field.InternalName;
- // check if the current field is on the list of "hidden" fields in the current display mode
- // or whether there is a "global" rule to hide fields
- FieldDisplayRuleItem exception = _dynamicRules.FirstOrDefault(
- // empty (null) value means there is no restriction for the control mode
- e => ((e.ControlModes == null) || (e.ControlModes.Contains(ControlMode))) &&
- // empty (null) value means there is no restriction for the field name
- ((e.FieldNames == null) || (e.FieldNames.Contains(fieldName))) &&
- (e.Rule == FieldDisplayRule.Hidden));
- if ((!this.IsFieldExcluded(field)) && (exception == null))
- {
- TemplateContainer child = new TemplateContainer();
- this.Controls.Add(child);
- SPControlMode controlMode = GetControlMode(fieldName);
- // use the dynamic setter to access internal properties
- set_TemplateContainer_ControlMode(child, controlMode);
- set_TemplateContainer_FieldName(child, fieldName);
- this.ControlTemplate.InstantiateIn(child);
- }
- }
- }
- private SPControlMode GetControlMode(string fieldName)
- {
- FieldDisplayRuleItem rule = _dynamicRules.FirstOrDefault(
- e => ((e.ControlModes == null) || (e.ControlModes.Contains(ControlMode))) &&
- ((e.FieldNames == null) || (e.FieldNames.Contains(fieldName))) &&
- (e.Rule == FieldDisplayRule.Display));
- SPControlMode result = (rule == null) ? ControlMode : SPControlMode.Display;
- return result;
- }
- }
Instead of using string literals in my methods, I usually use static classes to store string constants like field or list names:
- public static class SampleTaskListFields
- {
- public static readonly String Id = "ID";
- public static readonly String Title = "Title";
- public static readonly String PercentComplete = "PercentComplete";
- public static readonly String AssignedTo = "AssignedTo";
- public static readonly String Status = "Status";
- public static readonly String Predecessors = "Predecessors";
- public static readonly String StartDate = "StartDate";
- public static readonly String DueDate = "DueDate";
- }
- public static class Lists
- {
- public static readonly String SampleList = "SampleTaskList";
- }
- public static class TaskStates
- {
- // we only declare the state names used in the sample
- public static readonly String NotStarted = "Not Started";
- public static readonly String Completed = "Completed";
- }
If you prefer working with Guids then you may consider using the SPBuiltInFieldId class instead.
The exact implementation is derived from the BaseListFieldIterator class. We store static (global) rules in _staticRules and dynamic rules in _dynamicRules.
In the current example we are working with a task list and would like to achieve the following results:
Static rules:
- The Title field can be edited only when the task is created.
- Status and % Complete fields are hidden on task creation (we will set this values from our custom save handler method later).
Dynamic rules:
- Predecessors field is only editable by site administrators.
- Start Date is only editable if the item is in the Not Started status.
The following code creates the rules for these requirements.
- public class ListFieldIterator : BaseListFieldIterator
- {
- protected static List<FieldDisplayRuleItem> _staticRules = new List<FieldDisplayRuleItem>();
- static ListFieldIterator()
- {
- // task title can be set on item creation
- // but can't be altered later
- _staticRules.Add(
- new FieldDisplayRuleItem
- {
- FieldNames = new List<String>() { SampleTaskListFields.Title },
- ControlModes = new List<SPControlMode>() { SPControlMode.Edit },
- Rule = FieldDisplayRule.Display
- });
- // we don't allow to set status and % info on new item form
- // items will be created with status "Not Started" and 0 %
- // these values are set on item saving from code
- _staticRules.Add(
- new FieldDisplayRuleItem
- {
- FieldNames = new List<String>() { SampleTaskListFields.Status, SampleTaskListFields.PercentComplete },
- ControlModes = new List<SPControlMode>() { SPControlMode.New },
- Rule = FieldDisplayRule.Hidden
- });
- }
- protected override void OnInit(EventArgs e)
- {
- base.OnInit(e);
- // register save handler if not in display mode and form is posted back
- if ((Page.IsPostBack) && (ControlMode != SPControlMode.Display))
- {
- _formContext.OnSaveHandler += new EventHandler(SaveHandler);
- }
- CreateDynamicExceptions();
- }
- private void CreateDynamicExceptions()
- {
- _dynamicRules.AddRange(_staticRules);
- // only site admins are allowed to edit predecessor tasks
- // others can see only the predecessors regardless of the control mode
- if (!_web.CurrentUser.IsSiteAdmin)
- {
- _dynamicRules.Add(
- new FieldDisplayRuleItem
- {
- FieldNames = new List<String>() { SampleTaskListFields.Predecessors },
- Rule = FieldDisplayRule.Display
- });
- }
- // start date can be set only if the task is not started
- if ((String)Item[SampleTaskListFields.Status] != TaskStates.NotStarted)
- {
- _dynamicRules.Add(
- new FieldDisplayRuleItem
- {
- FieldNames = new List<String>() { SampleTaskListFields.StartDate },
- Rule = FieldDisplayRule.Display
- });
- }
- }
- protected void SaveHandler(object sender, EventArgs e)
- {
- Page.Validate();
- if (Page.IsValid)
- {
- // do custom activities, send mail, create task,
- // set permissions etc.
- // new tasks are created with status 'Not Started' and 0% complete
- if (ControlMode == SPControlMode.New)
- {
- Item[SampleTaskListFields.PercentComplete] = 0;
- Item[SampleTaskListFields.Status] = TaskStates.NotStarted;
- }
- // we should save the item explicitly
- Item.Update();
- }
- }
- }
In the OnInit method we register of custom save handler method. In the SaveHandler method we set the values for the Status and % Complete fields if the item is a new task. That is the method you can include your custom actions into, like sending mails or setting permissions on items.
To test the solution, we should first deploy the assembly as well as our user control. Then create a Tasks list called SampleTaskList, and register our custom ListFieldIterator to all of its forms as shown in my former post.
Logged on as a site admin, we create a task item.
Although the Status and % Complete fields are not visible on the form, the values of the fields are populated.
Next, we create another task item, and set the first one as a predecessor.
After saving the item, we open it again in edit mode. The Title field displayed as read-only.
We log in as a standard site member (non-admin), and open the item once again. The Predecessors field is also read-only in this case. We set the Status to In Progress.
After re-opening the item we see that the Start Date field became read-only.
I hope this example helps you to create SharePoint forms that fit better to the common customer needs. In the following posts I’m planning to build on this sample with other interesting features.
You can download the sample application from here.
Thanks Peter this wonderful post and sharing Knowledge.I am trying to create class library for baseFieldIterator and then use this in sharepoint project but its not working.i mean Form load but display nothing if i remove CustomFiledIterator from Tempate its work.
Please advise
Comment by Ronak — November 15, 2011 @ 20:13
Just Update if i put everything Under one Project it works but if try to make dll and then use it doesn’t work.
Comment by Ronak — November 15, 2011 @ 20:24
Ronak, try to download the sample application I’ve published here.
Comment by Peter Holpar — November 15, 2011 @ 21:01
Thanks Peter i am loving this u just open lost of opportunities to customize sharepoint List forms and just in time for me as i have req to hide fields based on member ship within group.
Once again thanks for Sample code.
Comment by Ronak — November 15, 2011 @ 21:26