Add a new Field Type (in System Settings)
The first step in creating a custom dropdown is registering a new field type within Sitecore's system settings. Navigate to /sitecore/system/Settings/Forms/Field Types in the Content Editor. The simplest approach is to duplicate the existing Dropdown List item and give it a meaningful name - for example OptionSet Dropdown List.
You will then need to update the following fields:
- View Path
- Model Type
- Property Editor
- Field Template
The sections below describe how to customise each of these areas. Once complete, we need to circle back to this item and update the relevant properties to point to our custom values.
Define a new FieldTemplate
Once your field type is registered, you need to create a corresponding field template that content authors will use when building forms. Field templates are stored in /sitecore/templates/System/Forms/Fields and define the data structure for your custom field.
Create a new template that inherits from the original template (/sitecore/templates/System/Forms/Fields/DropDownList). This inheritance ensures your custom dropdown retains all the standard functionality while allowing you to add new fields specific to your requirements.
Add any additional fields the custom dropdown needs. In our example we add the following:
- Show Empty Item (checkbox)
- Entity
- Attribute
- IgnoreList
- PriorityList
- Selected
- Hidden
- OnlyPriority
These properties allow us to define what data is requested from the Dynamics API, then additional options to enable us to customise which of the returned options are displayed within our dropdown.
Decompile and Extend DropDownListViewModel
To customize the behavior of thedropdown, we need to extend the base Sitecore.ExperienceForms.Mvc.Models.Fields.DropDownListViewModel class. Start by decompiling the Sitecore assemblies (using tools like dotPeek or ILSpy) to examine the original implementation and understand how it works.
Create a new class in your project that inherits from DropDownListViewModel:
[Serializable] public class OptionSetDropdownListViewModel : DropDownListViewModel { [NonSerialized] private IFieldSettingsManager<ListFieldItemCollection> _fieldSettingsManager; [NonSerialized] private IDynamicsEntityAccess _dynamicsEntityAccess; public string Entity { get; set; } public string Attribute { get; set; } public string IgnoreList { get; set; } public string PriorityList { get; set; } public string Selected { get; set; } public bool Hidden { get; set; } public bool OnlyPriority { get; set; } public OptionSetDropdownListViewModel() { _fieldSettingsManager = ServiceLocator.ServiceProvider.GetService<IFieldSettingsManager<ListFieldItemCollection>>(); _dynamicsEntityAccess = ServiceLocator.ServiceProvider.GetService<IDynamicsEntityAccess>(); } protected override void InitItemProperties(Item item) { Assert.ArgumentNotNull(item, "item"); base.InitItemProperties(item); Entity = StringUtil.GetString(item.Fields["Entity"]); Attribute = StringUtil.GetString(item.Fields["Attribute"]); IgnoreList = StringUtil.GetString(item.Fields["IgnoreList"]); PriorityList = StringUtil.GetString(item.Fields["PriorityList"]); Selected = StringUtil.GetString(item.Fields["Selected"]); Hidden = StringUtil.GetString(item.Fields["Hidden"]) == "1"; OnlyPriority = StringUtil.GetString(item.Fields["OnlyPriority"]) == "1"; } protected override void UpdateItemFields(Item item) { Assert.ArgumentNotNull(item, "item"); base.UpdateItemFields(item); item.Fields["Entity"]?.SetValue(Entity, force: false); item.Fields["Attribute"]?.SetValue(Attribute, force: false); item.Fields["IgnoreList"]?.SetValue(IgnoreList, force: false); item.Fields["PriorityList"]?.SetValue(PriorityList, force: false); item.Fields["Selected"]?.SetValue(Selected, force: false); item.Fields["Hidden"]?.SetValue(Hidden ? "1" : "0", force: false); item.Fields["OnlyPriority"]?.SetValue(OnlyPriority ? "1" : "0", force: false); } protected override IFieldSettingsManager<ListFieldItemCollection> DataSourceSettingsManager => new OptionSetModelBuilder(_fieldSettingsManager, _dynamicsEntityAccess); }
The overwritten InitItemProperties() and UpdateItemFields() methods simply deal with reading and saving the options found within the Forms GUI.
The DataSourceSettingsManager refers to another Customised class that we need to create called OptionSetModelBuilder. We create this class in a similar manner, by extending the original IFieldSettingsManager<ListFieldItemCollection> (referred to in DropDownListViewModel). This is the utility that we will use to bring in our data from Microsoft Dynamics.
[Serializable] public class OptionSetModelBuilder : IFieldSettingsManager<ListFieldItemCollection> { private IDynamicsEntityAccess _dynamicsEntityAccess; private IFieldSettingsManager<ListFieldItemCollection> _fieldSettingsManager; public OptionSetModelBuilder(IFieldSettingsManager<ListFieldItemCollection> fieldSettingsManager, IDynamicsEntityAccess dynamicsEntityAccess) { _dynamicsEntityAccess = dynamicsEntityAccess; _fieldSettingsManager = fieldSettingsManager; } public ListFieldItemCollection GetSettings(Item fieldItem) { try { return GetItems(fieldItem); } catch (Exception ex) { Sitecore.Diagnostics.Log.Error("OptionSetListDataSourceProvider:: Error getting pick list values", ex, this); return new ListFieldItemCollection(); } } public void SaveSettings(Item fieldItem, ListFieldItemCollection settings) { } protected ListFieldItemCollection GetItems(Item fieldItem) { var items = new ListFieldItemCollection(); try { var entity = StringUtil.GetString(fieldItem.Fields["Entity"]); var attribute = StringUtil.GetString(fieldItem.Fields["Attribute"]); var selected = StringUtil.GetString(fieldItem.Fields["Selected"]); if (!string.IsNullOrWhiteSpace(entity) && !string.IsNullOrWhiteSpace(attribute)) { var optionSetValues = _dynamicsEntityAccess.GetOptionSetValues(entity, attribute).OrderBy(kvp => kvp.Value, StringComparer.OrdinalIgnoreCase).ToList(); foreach (var item in optionSetValues) { items.Add(new ListFieldItem() { Value = item.Key.ToString(), Text = item.Value, Selected = item.Key.ToString() == selected ? true : false }); } } else { items.AddRange(new ListFieldItem[] { new ListFieldItem { Value = "Inital - no entity or attribute", Text = "Inital - no entity or attribute" } }); } } catch (Exception ex) { items.AddRange(new ListFieldItem[] { new ListFieldItem { Value = "Error", Text = "Error" } }); Log.Error("Error getting values", ex, this); } return items; } }
In this class you can see that we take the Entity and Attribute from the controls settings, then pass them to the IDynamicsEntityAccess wrapper to get our data. The end result is a collection of ListFieldItem objects (Value and Text), that we return, to be handed back to the view, to create our dropdown.
Duplicate and Customise the DropDownList View
The next thing we need to do is to define the view. As always, the simplest approach is to start with the OTB approach (i.e. DropDownList view) and add any additional functionality that is required:
@model SitecoreToDynamics.Models.SitecoreForms.FieldTypes.OptionSetDropdownListViewModel @{ var isHidden = Model.Hidden; var isInDesignMode = Sitecore.Context.Request.QueryString["sc_formmode"] != null; var ignoreList = Model.IgnoreList.Split('|').ToList() ?? new List<string>(); var priorityList = Model.PriorityList.Split('|').ToList() ?? new List<string>(); var originalItems = Model.Items; var orderedItems = new Sitecore.ExperienceForms.Mvc.Models.ListFieldItemCollection(); if (priorityList.Any()) { priorityList.Reverse(); foreach (var id in priorityList) { var match = originalItems.FirstOrDefault(i => i.Value == id); if (match != null && !orderedItems.Any(i => i.Value == match.Value)) { orderedItems.Add(match); } } if (!Model.OnlyPriority) { orderedItems.Add(new Sitecore.ExperienceForms.Mvc.Models.ListFieldItem { Value = "", Text = "---" }); } } if (!Model.OnlyPriority) { foreach (var item in originalItems) { if (!priorityList.Contains(item.Value) && !ignoreList.Contains(item.Value)) { orderedItems.Add(item); } } } } @if (isHidden && !isInDesignMode) { <input id="@Html.IdFor(m => Model.Value)" name="@Html.NameFor(m => Model.Value)" type="hidden" value="@Model.Selected" data-sc-tracking="@Model.IsTrackingEnabled" data-sc-field-name="@Model.Name" data-sc-field-key="@Model.ConditionSettings.FieldKey" @Html.GenerateUnobtrusiveValidationAttributes(m => m.Value) /> } else { <label for="@Html.IdFor(m => Model.Value)" class="@Model.LabelCssClassSettings.CssClass">@Html.DisplayTextFor(t => Model.Title) @(isHidden && isInDesignMode ? "(hidden)" : "") @(Model.Required ? "*" : "")</label> <select id="@Html.IdFor(m => Model.Value)" name="@Html.NameFor(m => Model.Value)" class="@Model.CssClassSettings.CssClass" data-sc-tracking="@Model.IsTrackingEnabled" data-sc-field-name="@Model.Name" data-sc-field-key="@Model.ConditionSettings.FieldKey" @Html.GenerateUnobtrusiveValidationAttributes(m => m.Value)> @if (Model.ShowEmptyItem) { <option label=" "></option> }@foreach (var item in orderedItems) { if (ignoreList.Contains(item.Value)) { continue; } string selectedText = ""; if (item.Value == Model.Selected) { selectedText = "selected"; } <option value="@item.Value" @selectedText>@item.Text</option> } </select> @Html.ValidationMessageFor(m => Model.Value) }
Some key things to note about the view are:
- The hidden property is taken from the viewmodel and used to optionally change the dropdown into a <input type=hidden> element. This allows people building the forms, to choose from a set of values coming from Dynamics (i.e. Source or Category), then hide the field from view.
- The isInDesignMode flag enables us to show the hidden field in the Forms GUI only
- The priority list allows us to promote items to the top of list
- The ignore list allows us to remove items entirely
- The OnlyPriority flag enables us to only add those values defined in the list. Using this checkbox in conjunction with the priority list, enables us to only display a select few items from a much larger list.
Create a new Property Editor in Core Database
The final step is creating a property editor in the Core database that allows content authors to configure the custom dropdown in the Forms designer. Switch to the Core database and navigate to /sitecore/client/Applications/FormsBuilder/Components/Layouts/PropertyGridForm/PageSettings/Settings.
Create a new item here by duplicating the existing DropDownList item. You will notice a child item called 'Details', under this item add a new FormTextBox Parameters item for each of the attributes defined in the template (i.e. Attribute, Entity etc). For the boolean properties, you should use the FormDropList Parameters template (i.e. Hidden, OnlyPriority).
Note: If you have any difficulty with this, you should copy from an existing editor.
On each of the new attributes (under details), you need to update the FormLabel and BindingConfiguration (I found that the binding configuration needed to be camel case… as below).
Then navigate back to the Details item, and add each of the newly created attributes to the ControlDefinitions section (see below).
Finally, link this property editor back to your field type item by updating the Property Editor field in the field type you created in step one.
Add the new DropDown list to a Form
If you have completed all of the steps above, then updated each of the fields on the new field type with your custom values, you should then see the new dropdownlist as an optnio on the right hand side of the Forms GUI.
Try adding one onto the forms canvas, select the property again and values for Entity and Attribute (as a minimum). Hit apply and then save the form itself.
Hopefully, you should then see that the forms has populated your new dropdown with values from Dynamics. If so, try adding some values (pipe separated list) into the priority or ignore list, to see what happens.
One more thing…
If at some point you want to apply Conditional Rules to your new field, then you need to make one more small adjustment to enable this.
Go to both:
/sitecore/system/Settings/Forms/Meta Data/Conditions/Operators/is equal to
and also
/sitecore/system/Settings/Forms/Meta Data/Conditions/Operators/is not equal to
Then add your new field type to the Allowed Field Types property on each. This tells the conditional rules dialog box in the Form GUI, which rules can be applied to your new Field Type.
This article has hopefully shown you how to pull OptionSet data directly from Dynamics and present it as a dropdown list for a user to select from. Thus ensuring that the selected option exactly matches the format (i.e. Int or Guid) that is required to save the selection back to a Dynamics entity.
Whilst this article focused mainly on a Dynamics OptionSet, the process is very similar for creating a dropdown to display EntitySet options. You can find example of each in the SitecoreToDynamics repository.